#include "cinputcontext.h"

#include "cinputaudiostream.h"
#include "cinputvideostream.h"

namespace buola { namespace media { namespace ffmpeg {

int CInputContext::ReadCB(void* pClosure, uint8_t* pBuf, int pSize)
{
    CInputContext *lInfo=reinterpret_cast<CInputContext*>(pClosure);
    
    CBuffer<uint8_t> lBuffer(pBuf,pSize);

    lBuffer.SetOffset(lInfo->mInOffset);

    if(!lInfo->mInStream->PullBuffer(lBuffer))
    {
        msg_log() << "can't pull buffer\n";
        return AVERROR_EOF;
    }

    lInfo->mInOffset+=lBuffer.Size();

    return lBuffer.Size();
}

int64_t CInputContext::SeekCB(void* pClosure, int64_t pPos, int pWhence)
{
    CInputContext *lInfo=reinterpret_cast<CInputContext*>(pClosure);

    switch(pWhence)
    {
    case SEEK_SET:
        lInfo->mInOffset=pPos;
        break;
    case SEEK_CUR:
        lInfo->mInOffset+=pPos;
        break;
    case SEEK_END:
    case AVSEEK_SIZE:
        {
            off_t lSize=lInfo->mInStream->QueryDuration();

            if(pWhence==AVSEEK_SIZE)
                return lSize;

            lInfo->mInOffset=lSize+pPos;
            break;
        }
    default:
        msg_error() << "unknown whence in buolain_protocol_seek\n";
        break;
    }

    return lInfo->mInOffset;
}

CInputContext::CInputContext()
{
    mCtx=avformat_alloc_context();
}

void CInputContext::Open(blackbox::CStreamIn<uint8_t> &pIn)
{
    mInStream=&pIn;
    mInOffset=0;
    mCtx->pb=avio_alloc_context(alloc_array<uint8_t>(4096),4096,0,this,ReadCB,nullptr,SeekCB);
    int lRet=avformat_open_input(&mCtx,nullptr,nullptr,nullptr);
    if(lRet<0) throw XInternal("can't open ffmpeg file\n");
}

void CInputContext::FindStreams()
{
    ///\TODO:should use a mutex here?
    int lRet=avformat_find_stream_info(mCtx,nullptr);
    if(lRet<0) throw XInternal("can't get stream info "+to_string(lRet));

    for(int i=0;i<mCtx->nb_streams;i++)
    {
        AVStream *lAV=mCtx->streams[i];

        if(lAV->codec->codec_type==AVMEDIA_TYPE_VIDEO)
        {
            msg_info() << "found video stream\n";
            CInputVideoStream *lVideo=new CInputVideoStream(lAV);
            mVideo.push_back(lVideo);
            mStreams[i]=lVideo;
        }
        else if(lAV->codec->codec_type==AVMEDIA_TYPE_AUDIO)
        {
            msg_info() << "found audio stream\n";
            CInputAudioStream *lAudio=new CInputAudioStream(lAV);
            mAudio.push_back(lAudio);
            mStreams[i]=lAudio;
        }
        else if(lAV->codec->codec_type==AVMEDIA_TYPE_SUBTITLE)
        {
            msg_info() << "ignoring subtitle stream\n";
        }
        else
        {
            msg_log() << "found unknown stream:" << (int)lAV->codec->codec_type << "\n";
        }
    }
}

void CInputContext::ProcessNextFrame()
{
    AVPacket lPacket;

    int lRes=av_read_frame(mCtx,&lPacket);
    if(lRes<0) throw XHardware("can't read frame!\n");

    auto lStream=mStreams.find(lPacket.stream_index);
    
    if(lStream!=mStreams.end())
    {
        lStream->second->ProcessPacket(&lPacket);
    }
    
    if(lPacket.destruct)
        lPacket.destruct(&lPacket);
}

/*namespace ffmpeg*/ } /*namespace media*/ } /*namespace buola*/ }
